/* 
 *  Copyright (C) 2008  Charlotte Curtis <c.f.curtis@gmail.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  The Rhythmbox authors hereby grants permission for non-GPL compatible
 *  GStreamer plugins to be used and distributed together with GStreamer
 *  and Rhythmbox. This permission is above and beyond the permissions granted
 *  by the GPL license by which Rhythmbox is covered. If you modify this code
 *  you may extend this exception to your version of the code, but you are not
 *  obligated to do so. If you do not wish to do so, delete this exception
 *  statement from your version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 *
 */


#include <config.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <math.h>
#include <string.h>

#include <glib/gi18n-lib.h>
#include <glib.h>
#include <glib-object.h>
#include <gio/gio.h>
#include <glib/gstdio.h>


/* Rhythmbox headers */
#include "rb-debug.h"
#include "rb-plugin.h"
#include "rb-shell.h"
#include "rb-shell-player.h"
#include "rhythmdb.h"
#include "rb-source.h"
#include "rb-statusbar.h"
#include "rb-static-playlist-source.h"
#include "eel-gconf-extensions.h"
#include "rb-util.h"

/* In the mood headers */
#include "rb-inthemood-source.h"

/* gconf variables */
#define CONF_ITM_PREFIX  CONF_PREFIX "/plugins/inthemood"
#define CONF_SCAN_COMPLETE CONF_ITM_PREFIX "/scan_complete"
#define CONF_SCAN_PAUSED CONF_ITM_PREFIX "/scan_paused"
#define CONF_VEC_FOLDER CONF_ITM_PREFIX "/vec_folder"
#define CONF_SCAN_ITER_PATH CONF_ITM_PREFIX "/scan_iter_path"

#define RB_TYPE_INTHEMOOD_PLUGIN         (rb_inthemood_plugin_get_type ())
#define RB_INTHEMOOD_PLUGIN(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_INTHEMOOD_PLUGIN, RBInTheMoodPlugin))
#define RB_INTHEMOOD_PLUGIN_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_INTHEMOOD_PLUGIN, RBInTheMoodPluginClass))
#define RB_IS_INTHEMOOD_PLUGIN(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_INTHEMOOD_PLUGIN))
#define RB_IS_INTHEMOOD_PLUGIN_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_INTHEMOOD_PLUGIN))
#define RB_INTHEMOOD_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_INTHEMOOD_PLUGIN, RBInTheMoodPluginClass))

typedef struct
{
	/* Rhythmbox objects */
	RBPlugin parent;
	RBSource *source;
	RBShellPlayer *player;
	RhythmDB *db;
	RBEntryView *entryview;
	RBStatusbar* statusbar;
	
	/* Gconf stuff */
	gboolean scan_complete;
	gboolean scan_paused;
	gchar *vec_folder;
	
	/* Stuff to use while running */
	char *fextract_path;
	char *fcompare_path;
	GAsyncQueue *uris_to_scan;
	GAsyncQueue *scanned_uris;
	GAsyncQueue *uris_in_source;
	GHashTable *blacklist;
	char *seed_uri;
	GPid fextract_pid;
	GPid fcompare_pid;
	double num_songs;
	
	/* signal handler ids */
	gulong entry_added_id;
	gulong entry_deleted_id;
	gulong load_complete_id;
	gulong playing_song_changed_id;
	gulong song_added_id;
	guint ui_merge_id;
} RBInTheMoodPlugin;

typedef struct
{
	RBPluginClass parent_class;
} RBInTheMoodPluginClass;

typedef struct
{
	double val;
	guint outpipe;
} ComparisonResult;

G_MODULE_EXPORT GType register_rb_plugin (GTypeModule *module);
GType rb_inthemood_plugin_get_type (void) G_GNUC_CONST;

/* necessary Rhythmbox function prototypes */
static void rb_inthemood_plugin_init (RBInTheMoodPlugin *plugin);
static void rb_inthemood_plugin_finalize (GObject *object);
static void impl_activate (RBPlugin *plugin, RBShell *shell);
static void impl_deactivate (RBPlugin *plugin, RBShell *shell);

/* callback function prototypes */
static void rb_inthemood_entry_added_cb (RBEntryView *view, RhythmDBEntry *entry, RBInTheMoodPlugin *pi);
static void rb_inthemood_entry_deleted_cb (RBEntryView *view, RhythmDBEntry *entry, RBInTheMoodPlugin *pi);
static void rb_inthemood_load_complete_cb (RhythmDB *db, RBInTheMoodPlugin *pi);
static void rb_inthemood_playing_song_changed_cb (RBShellPlayer *player, RhythmDBEntry *entry, RBInTheMoodPlugin *pi);
static void rb_inthemood_song_added_cb (RhythmDB *db, RhythmDBEntry *entry, RBInTheMoodPlugin *pi);

/* analysis function prototypes */
static gpointer analysis_thread (RBInTheMoodPlugin *pi);
static gboolean catch_child (GPid pid, gint status, gpointer data);
static gboolean process_timed_out (GPid pid);

/* song matching function prototypes */
static gpointer comparison_thread (RBInTheMoodPlugin *pi);
static double compute_euclidean_distance (GArray *vec1, GArray *vec2);
static GArray *read_vecfile (char *filename);

/* utility function prototypes */
static char *construct_vecfilename (RBInTheMoodPlugin *pi, char *uri);
static gboolean update_statusbar (RBInTheMoodPlugin *pi);
static void read_blacklist (RBInTheMoodPlugin *pi);
static void write_blacklist (RBInTheMoodPlugin *pi);
static gpointer pop_str_from_queue (GAsyncQueue *queue, char *str);

RB_PLUGIN_REGISTER (RBInTheMoodPlugin, rb_inthemood_plugin);

static void
rb_inthemood_plugin_class_init (RBInTheMoodPluginClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	RBPluginClass *plugin_class = RB_PLUGIN_CLASS (klass);

	object_class->finalize = rb_inthemood_plugin_finalize;

	plugin_class->activate = impl_activate;
	plugin_class->deactivate = impl_deactivate;
}

static void
rb_inthemood_plugin_init (RBInTheMoodPlugin *plugin)
{
	rb_debug ("RBInTheMoodPlugin initialising");
}

static void
rb_inthemood_plugin_finalize (GObject *object)
{
	rb_debug ("RBInTheMoodPlugin finalising");

	G_OBJECT_CLASS (rb_inthemood_plugin_parent_class)->finalize (object);
}

static void
impl_activate (RBPlugin *plugin, RBShell *shell)
{	
	RBInTheMoodPlugin *pi = RB_INTHEMOOD_PLUGIN (plugin);
	/* Pause for now so everything can be initialized */
	pi->fextract_path = rb_plugin_find_file (plugin, "fextract");
	pi->fcompare_path = rb_plugin_find_file (plugin, "fcompare");
	pi->uris_to_scan = g_async_queue_new ();
	pi->scanned_uris = g_async_queue_new ();
	pi->uris_in_source = g_async_queue_new ();
	pi->blacklist = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
	pi->fextract_pid = -1;
	pi->fcompare_pid = -1;
	
	/* Get the objects that we need */
	GtkUIManager *uimanager;
	g_object_get (shell, "shell-player", &pi->player, "db", &pi->db, 
			"ui-manager", &uimanager, NULL);
	g_object_get (pi->player, "statusbar", &pi->statusbar, NULL);
	
	/* set up the ui stuff */
	char * uifile = rb_plugin_find_file (plugin, "inthemood-ui.xml");
	pi->ui_merge_id = gtk_ui_manager_add_ui_from_file (uimanager, uifile, NULL);
	g_free (uifile);
	g_object_unref (uimanager);
	
	pi->source = rb_inthemood_source_new (shell);
	rb_shell_append_source (shell, pi->source, NULL);
	pi->entryview = rb_source_get_entry_view (pi->source);

	/* read in gconf values */
	pi->scan_complete = eel_gconf_get_boolean (CONF_SCAN_COMPLETE);
	pi->vec_folder = eel_gconf_get_string (CONF_VEC_FOLDER);

//TODO: check if we have write permissions to this folder
	
	if (pi->vec_folder == NULL) 
	{
		gchar *homedir = (gchar *)g_get_home_dir();
		pi->vec_folder = g_build_filename (homedir,".gnome2", "rhythmbox",
				 "inthemood", NULL);
	//	g_free (homedir);
	}
	
	rb_debug ("Vec folder set as %s", pi->vec_folder);
	
	/* connect signal callbacks */
	pi->entry_added_id = g_signal_connect (pi->entryview, "entry-added",
			G_CALLBACK (rb_inthemood_entry_added_cb), pi);
			
	pi->entry_deleted_id = g_signal_connect (pi->entryview, "entry-deleted",
			G_CALLBACK (rb_inthemood_entry_deleted_cb), pi);
	
	pi->playing_song_changed_id =  g_signal_connect_after (pi->player, "playing-song-changed",
			G_CALLBACK (rb_inthemood_playing_song_changed_cb), pi);
		
	pi->load_complete_id = g_signal_connect_after (pi->db, "load-complete", 
			G_CALLBACK (rb_inthemood_load_complete_cb), pi);
	
	rb_debug ("Callbacks set");
}

static void
impl_deactivate (RBPlugin *plugin, RBShell *shell)
{	
	RBInTheMoodPlugin *pi = RB_INTHEMOOD_PLUGIN (plugin);
	
	gint status;
	waitpid (pi->fextract_pid, &status, WNOHANG);
	write_blacklist (pi);
	
	/* disconnect signal handlers */
	g_signal_handler_disconnect (pi->entryview, pi->entry_added_id);
	g_signal_handler_disconnect (pi->player, pi->playing_song_changed_id);
	g_signal_handler_disconnect (pi->db, pi->load_complete_id);
	g_signal_handler_disconnect (pi->db, pi->song_added_id);
	
	/* remove ui */
	GtkUIManager *uimanager;
	g_object_get (shell, "ui-manager", &uimanager, NULL);
	gtk_ui_manager_remove_ui (uimanager, pi->ui_merge_id);
	g_object_unref (uimanager);
	
	/* Store all configuration options */
	eel_gconf_set_boolean (CONF_SCAN_COMPLETE, pi->scan_complete);
	eel_gconf_set_boolean (CONF_SCAN_PAUSED, pi->scan_paused);
	eel_gconf_set_string (CONF_VEC_FOLDER, pi->vec_folder);
	
	if (pi->source) {
		rb_source_delete_thyself (pi->source);
		pi->source = NULL;
		rb_debug ("Deleted In The Mood Source");
	}

	if (pi->vec_folder != NULL) g_free (pi->vec_folder);
}

/* Entry added to the In The Mood playlist */
static void rb_inthemood_entry_added_cb (RBEntryView *view, RhythmDBEntry *entry,
		RBInTheMoodPlugin *pi)
{			
	rb_debug ("Entry added to In The Mood source");
	char *uri = rhythmdb_entry_get_playback_uri (entry);
	char *vecpath = construct_vecfilename (pi, uri);
	gboolean file_exists = g_file_test (vecpath, G_FILE_TEST_EXISTS);
	if (!file_exists)
	{
		rb_debug ("That song hasn't been analyzed yet!  \
				I'll add it, but it won't be used for choosing the next song");
		rb_inthemood_source_remove_entry (RB_INTHEMOOD_SOURCE (pi->source), entry);
		/* this fails for some reason, just let the song be added but choose the next based on
		the previous entry */
	} else {
		rb_debug ("updating seed uri to be %s", uri);
		pi->seed_uri = uri;
		gpointer item = pop_str_from_queue (pi->scanned_uris, uri);
		if (item)
			g_async_queue_push (pi->uris_in_source, item);
	}
	
	rb_debug ("scanned_uris: %d", g_async_queue_length (pi->scanned_uris));
	rb_debug ("uris_in_source: %d", g_async_queue_length (pi->uris_in_source));
	g_free (vecpath);
}

static void rb_inthemood_entry_deleted_cb (RBEntryView *view, RhythmDBEntry *entry, 
		RBInTheMoodPlugin *pi)
{
	char *uri = rhythmdb_entry_get_playback_uri (entry);
	gpointer item = pop_str_from_queue (pi->uris_in_source, uri);
	if (item)
		g_async_queue_push (pi->scanned_uris, item);
	
	rb_debug ("scanned_uris: %d", g_async_queue_length (pi->scanned_uris));
	rb_debug ("uris_in_source: %d", g_async_queue_length (pi->uris_in_source));
}	


/* Entry added to the library database */
static void rb_inthemood_song_added_cb (RhythmDB *db, RhythmDBEntry *entry, 
		RBInTheMoodPlugin *pi)
{	
	pi->scan_complete = FALSE;
	
	/* Get the mimetype to make sure it can be analyzed */
	const char *mimetype = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MIMETYPE);
	
	/* push the uri of the entry onto the list to be scanned */
	if (!(strstr (mimetype, "ogg") == 0) || !strstr (mimetype, "x-id3") == 0)
		g_async_queue_push (pi->uris_to_scan, 
			(gpointer)rhythmdb_entry_get_playback_uri (entry));
}


static void rb_inthemood_load_complete_cb (RhythmDB *db, RBInTheMoodPlugin *pi)
{		
	RhythmDBQueryModel *model = rhythmdb_query_model_new_empty (pi->db);
        
	rhythmdb_do_full_query (pi->db, RHYTHMDB_QUERY_RESULTS (model),
					RHYTHMDB_QUERY_PROP_LIKE, RHYTHMDB_PROP_MIMETYPE, "ogg",
					RHYTHMDB_QUERY_DISJUNCTION,
					RHYTHMDB_QUERY_PROP_LIKE, RHYTHMDB_PROP_MIMETYPE, "x-id3",
					RHYTHMDB_QUERY_DISJUNCTION,
					RHYTHMDB_QUERY_PROP_LIKE, RHYTHMDB_PROP_MIMETYPE, "mpeg",
					RHYTHMDB_QUERY_END);

	GtkTreeIter iter;
	RhythmDBEntry *entry;
	gboolean valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter);
	
	rb_debug ("Reading blacklist");
	read_blacklist (pi);
	
	char *uri;
	while (valid)
	{
		entry = rhythmdb_query_model_iter_to_entry (model, &iter);
		uri = rhythmdb_entry_get_playback_uri (entry);
		
		if (g_hash_table_lookup (pi->blacklist, (gconstpointer)uri) == NULL)
		{
			if (pi->scan_complete)
				g_async_queue_push (pi->scanned_uris, (gpointer)uri);
			else
				g_async_queue_push (pi->uris_to_scan, (gpointer)uri);
		}
		valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (model), &iter);
	}
	
	rb_debug ("%d songs already analyzed", g_async_queue_length (pi->scanned_uris));
	
	if (pi->uris_to_scan)
	{
		pi->num_songs = (double)g_async_queue_length (pi->uris_to_scan);
		rb_debug ("%10.0f songs left to go", pi->num_songs);
	}
	
	pi->song_added_id = g_signal_connect_after (pi->db, "entry-added", 
		G_CALLBACK (rb_inthemood_song_added_cb), pi);
	
	pi->scan_paused = eel_gconf_get_boolean (CONF_SCAN_PAUSED);
	if (pi->scan_paused || pi->scan_complete) return;
	else
	{
		rb_debug ("Scanning library");
		rb_statusbar_set_progress (pi->statusbar, 0.0,_("In The Mood analysis in progress"));
		g_timeout_add_seconds (2, (GSourceFunc)update_statusbar, (gpointer)pi);
		g_thread_create ((GThreadFunc) analysis_thread, pi, FALSE, NULL);
	}
}

static void rb_inthemood_playing_song_changed_cb (RBShellPlayer *player,
		RhythmDBEntry *entry, RBInTheMoodPlugin *pi)
{
	if (rb_shell_player_get_playing_source (player) == pi->source)
	{
		rb_debug ("Playing from In The Mood playlist");
		char *uri = rhythmdb_entry_get_playback_uri (entry);
		/* this is "evolution" mode */
		if (rb_safe_strcmp (pi->seed_uri, uri) == 0)
		{
			GThread *thread;
			thread = g_thread_create ((GThreadFunc) comparison_thread, pi, FALSE, NULL);
		} else
			rb_debug ("Seed song is %s, not %s", pi->seed_uri, uri);
	} else {
		rb_debug ("Not playing from In The Mood playlist");
	}
}

static gpointer comparison_thread (RBInTheMoodPlugin *pi)
{
	char *seedvecname = construct_vecfilename (pi, pi->seed_uri);
	char *compvecname = NULL;
	char *top_uri = NULL;
	gdouble current_val = 0.0;
	gdouble top_match_val = 100.0;
	guint counter = 0;
	GPtrArray *temp_storage_array = g_ptr_array_sized_new (g_async_queue_length (pi->scanned_uris));
	GTimeVal timeout_time;
	GArray *seedvec = read_vecfile (seedvecname);
	GArray *compvec = NULL;
  	
	g_get_current_time (&timeout_time);
	g_time_val_add (&timeout_time, 5000);
	char *compuri = g_async_queue_timed_pop (pi->scanned_uris, &timeout_time);
	rb_debug ("Starting with uri %s", compuri);
	
	while (compuri != NULL) {
		compvecname = construct_vecfilename (pi, compuri);
		if (g_file_test (compvecname, G_FILE_TEST_EXISTS))
		{
			compvec = read_vecfile (compvecname);
			current_val = compute_euclidean_distance (seedvec, compvec);			
			
			if (current_val < top_match_val && current_val > 0.0)
			{
				rb_debug ("New winner %s! %f beats %f", compuri, current_val, top_match_val);
				top_match_val = current_val;
				top_uri = compuri;
				counter ++;
			}
		}
		g_ptr_array_add (temp_storage_array, (gpointer)compuri);
		g_get_current_time (&timeout_time);
		g_time_val_add (&timeout_time, 5000);
		compuri = g_async_queue_timed_pop (pi->scanned_uris, &timeout_time);
	}
	
	if (top_uri != NULL)
	{
		rb_debug ("Adding %s to playlist", top_uri);
		rb_inthemood_source_add_location (RB_INTHEMOOD_SOURCE (pi->source), top_uri, -1);
	}
	else
		rb_debug ("Couldn't find a match!");
	
	rb_debug ("Pushing the scanned items back to their queue");
	
	gpointer uri;
	
	while ((uri = g_ptr_array_remove_index_fast (temp_storage_array, temp_storage_array->len - 1)))
	{
		if (!uri)
			break;
		g_async_queue_push (pi->scanned_uris, uri);
	}
	
	g_free (compvecname);
	g_free (seedvecname);
	g_array_free (seedvec, TRUE);
	g_array_free (compvec, TRUE);
	g_ptr_array_free (temp_storage_array, TRUE);
	rb_debug ("Comparison complete");
		
	return NULL;
}

static char *construct_vecfilename (RBInTheMoodPlugin *pi, char *uri)
{
	GFile *songfile;
	char *songfile_path;
	char *retval;
	
//TODO: add some error handling in here
	songfile = g_file_new_for_uri (uri);
	songfile_path = g_file_get_path (songfile);
	retval = g_build_filename (pi->vec_folder, songfile_path, NULL);
	
	g_free (songfile_path);
	return retval;
}

static gpointer analysis_thread (RBInTheMoodPlugin *pi)
{
	if (pi->scan_paused) return NULL;
	
	GFile *songfile = NULL;
	GFile *parent = NULL;
	gchar *songfile_path = "";
	gchar *vecfile_path = "";
	gchar *parent_path = "";
	gchar *subfolder_path = "";
	gboolean do_analysis = TRUE;
	gboolean uri_is_good = TRUE;
	double songs_done = 1.0;
	GTimeVal timeout_time;
	
	/* Variables needed for the spawned process */
	char *argv[4];
	gint outpipe;
	gint exit_status;
	gboolean successful_spawn;
	guint timeout_id;
	guint watch_id;
	
	g_get_current_time (&timeout_time);
	g_time_val_add (&timeout_time, 5000);
	char *uri = g_async_queue_timed_pop (pi->uris_to_scan, &timeout_time);
	rb_debug ("Starting with uri %s", uri);
	
	while (uri != NULL) {
		if (pi->scan_paused) break;
		songfile = g_file_new_for_uri (uri);
		
		/* keep the audio file extension - might be a little confusing, but will
		 * make it much easier to find the song file when needed */
		songfile_path = g_file_get_path (songfile);
		rb_debug ("got songfile path %s", songfile_path);
		vecfile_path = g_build_filename (pi->vec_folder, songfile_path, NULL);
		rb_debug ("Checking if %s exists", vecfile_path);
		
		gboolean file_exists = g_file_test (vecfile_path, G_FILE_TEST_EXISTS);
		
		if (file_exists)
		{
			do_analysis = FALSE;
			rb_debug ("Nope, skip this one");
		} else
			rb_debug ("Needs analysis, let's make sure the subfolder path exists");
		
		parent = g_file_get_parent (songfile);	
		parent_path = g_file_get_path (parent);
		
		/* Create the subfolder structure for the song - a little weird using the 
		 * entire path including /, but safest as songs could be stored anywhere */
		subfolder_path = g_build_filename (pi->vec_folder, parent_path, NULL);
		
		int mkdir_result = g_mkdir_with_parents (subfolder_path, 0777);
		if (mkdir_result == -1)
		{
			rb_debug ("Error creating subfolders");
			do_analysis = FALSE;
		} else
			rb_debug ("Subfolders all ready");
		
		if (do_analysis)
		{
			/* Initialize process spawning variables */
			argv[0] = pi->fextract_path;
			argv[1] = songfile_path;
			argv[2] = vecfile_path;
			argv[3] = NULL;
			
			pi->fextract_pid = -1;
			exit_status = -1;
			outpipe = -1;
			successful_spawn = FALSE;
			timeout_id = 0;
			watch_id = 0;

			successful_spawn = g_spawn_async_with_pipes (NULL, argv, NULL, 
				G_SPAWN_STDERR_TO_DEV_NULL|G_SPAWN_DO_NOT_REAP_CHILD, NULL, 
				NULL, &pi->fextract_pid, NULL, &outpipe, NULL, NULL);
			watch_id = g_child_watch_add (pi->fextract_pid, (GChildWatchFunc)catch_child, NULL);
			timeout_id = g_timeout_add_seconds (5, (GSourceFunc)process_timed_out, 
				(gpointer)pi->fextract_pid);
			waitpid (pi->fextract_pid, &exit_status, 0);
			g_spawn_close_pid (pi->fextract_pid);
			g_source_remove (timeout_id);
			g_source_remove (watch_id);
			pi->fextract_pid = -1;
			
			if (successful_spawn && exit_status == 0)
			{
				rb_debug ("Analysis completed for %s", vecfile_path);
			} 
			else
			{
				rb_debug ("That one screwed up, removing vector file if it was created");
				if (g_file_test (vecfile_path, G_FILE_TEST_EXISTS))
					g_remove (uri);
				g_hash_table_insert (pi->blacklist, (gpointer)uri, (gpointer)TRUE);
				uri_is_good = FALSE;
			}
		}
		
		g_get_current_time (&timeout_time);
		g_time_val_add (&timeout_time, 5000);
		if (uri_is_good)
			g_async_queue_push (pi->scanned_uris, (gpointer)uri);
		uri = g_async_queue_timed_pop (pi->uris_to_scan, &timeout_time);
		songs_done = songs_done + 1.0;
		do_analysis = TRUE;
		uri_is_good = TRUE;
		if (uri == NULL)
			rb_debug ("timed pop returned null!");
	}
	
	rb_debug ("Analysis complete for %1.0f songs", songs_done);
	pi->scan_complete = TRUE;

	g_object_unref (songfile);
	g_object_unref (parent);
	g_free (uri);
	g_free (songfile_path);
	g_free (vecfile_path);
	g_free (parent_path);
	g_free (subfolder_path);
	return NULL;
}

static gboolean process_timed_out (GPid pid)
{
	rb_debug ("Process timed out!  Killing process");
	kill (pid, SIGKILL);
	return FALSE;
}

static gboolean catch_child (GPid pid, gint status, gpointer data)
{
	rb_debug ("Child with pid %d caught, exiting with status %d", pid, status);
	return FALSE;
}

static gboolean update_statusbar (RBInTheMoodPlugin *pi)
{
	rb_debug ("Updating status bar");
	double songs_done = pi->num_songs - (double)g_async_queue_length (pi->uris_to_scan);
	rb_statusbar_set_progress (pi->statusbar, songs_done/pi->num_songs,
		_("In The Mood analysis in progress"));
	if (songs_done == pi->num_songs)
	{	
		rb_statusbar_set_progress (pi->statusbar, -1, "");
		return FALSE;
	}
	else
		return TRUE;
}

static void read_blacklist (RBInTheMoodPlugin *pi)
{
	GError *error = NULL;
	gchar *blacklist_filename = g_build_filename (pi->vec_folder, "blacklist", NULL);
	GIOChannel *bl_channel = g_io_channel_new_file (blacklist_filename, "r", &error);
	
	rb_debug ("Created channel");
	
	if (bl_channel)
	{	
		gchar *line;
		while (g_io_channel_read_line(bl_channel, &line, NULL, NULL, &error) == G_IO_STATUS_NORMAL)
		{	
			g_hash_table_insert (pi->blacklist, (gpointer)line, (gpointer)TRUE);
		}
		rb_debug ("Read in blacklist containing %d songs", g_hash_table_size (pi->blacklist));
		g_io_channel_unref (bl_channel);
		g_free (line);
	} else {
		rb_debug ("No blacklist yet");
	}
	g_free (blacklist_filename);
}
	
static void write_blacklist (RBInTheMoodPlugin *pi)
{
	GError *error = NULL;
	gchar *blacklist_filename = g_build_filename (pi->vec_folder, "blacklist", NULL);
	GIOChannel *bl_channel = g_io_channel_new_file (blacklist_filename, "w", &error);
	if (bl_channel)
	{	
		GHashTableIter iter;
		gpointer key;
		gpointer value;
		gsize bytes_written;
		g_hash_table_iter_init (&iter, pi->blacklist);
		while (g_hash_table_iter_next (&iter, &key, &value))
		{	
			g_io_channel_write_chars (bl_channel, g_strconcat(key, "\r\n", NULL),
				-1, &bytes_written, &error);
		}
		g_io_channel_unref (bl_channel);
	} else {
		rb_debug ("Error opening blacklist for writing");
	}
	g_free (blacklist_filename);
}

static GArray *read_vecfile (char *filename)
{
	GError *error = NULL;
	GIOChannel *channel = g_io_channel_new_file (filename, "r", &error);
	GArray *vec = g_array_new (FALSE, TRUE, sizeof(double));
	
	if (channel)
	{
		gchar *line;
		double val = 0.0;
		while (g_io_channel_read_line(channel, &line, NULL, NULL, &error) == G_IO_STATUS_NORMAL)
		{
			if (g_ascii_isdigit (line[0]))
			{
				val = g_ascii_strtod (line, NULL);
				vec = g_array_append_val(vec, val);
			}
		}
		g_io_channel_unref (channel);
		g_free (line);
	}
	return vec;
}

static double compute_euclidean_distance (GArray *vec1, GArray *vec2)
{
	if (vec1->len != vec2->len)
	{
		rb_debug ("Error! Vectors are not the same length.");
		return -1;
	}
	
	int i;
	double sum_of_squares = 0.0;
	double v1val = 0.0;
	double v2val = 0.0;
	
	for (i = 0; i < vec1->len; i++)
	{
		v1val = g_array_index (vec1, double, i);
		v2val = g_array_index (vec2, double, i);
		sum_of_squares += (v1val - v2val)*(v1val - v2val);
	}
	
	return sqrt (sum_of_squares);
}

static gpointer pop_str_from_queue (GAsyncQueue *queue, char *str)
{	
	GTimeVal timeout_time;
	GPtrArray *temp_storage_array = g_ptr_array_sized_new (g_async_queue_length (queue));
	g_get_current_time (&timeout_time);
	g_time_val_add (&timeout_time, 5000);
	gpointer item = g_async_queue_timed_pop (queue, &timeout_time);
	gpointer retval = NULL;
	
	while (item)
	{
		if (rb_safe_strcmp ((char *)item, str) == 0)
		{
			retval = item;
			break;
		}
		else
			g_ptr_array_add (temp_storage_array, item);
		g_get_current_time (&timeout_time);
		g_time_val_add (&timeout_time, 5000);
		item = g_async_queue_timed_pop (queue, &timeout_time);
	}
	while ((item = g_ptr_array_remove_index_fast (temp_storage_array, temp_storage_array->len - 1)))
	{
		if (!item)
			break;
		g_async_queue_push (queue, item);
	}
	g_ptr_array_free (temp_storage_array, TRUE);
	return retval;
}
